package pl.wwysocki.gendok;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.Id;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;
import org.odftoolkit.odfdom.doc.text.OdfTextUserFieldGet;
import org.w3c.dom.Node;

public class CollRef extends FieldRef
{
	private static final Log log = Logging.getLog(CollRef.class);
	Map<Node, TableReference> tables = new HashMap<Node, TableReference>();
	private EntityManager entityManager;
	private CollRefStack collRefStack;

	public CollRef(String name, Node element, Method getter)
	{
		super();
		entityManager = (EntityManager) Component.getInstance("entityManager");
		collRefStack = CollRefStack.getInstance();
		setName(name);
		setGetter(getter);
		setTableDef(getCollTableDef(element));
	}

	@Override
	public Object getIdentifier()
	{
		return getGetter();
	}

	private Node getCollTableDef(Node element)
	{
		Node t2 = null;
		Node t1 = null;
		try
		{
			// paragraf cell row table
			t2 = element.getParentNode().getParentNode().getParentNode().getParentNode();
			// cell row table
			t1 = t2.getParentNode().getParentNode().getParentNode();
		}
		catch (Throwable t)
		{
			log.error("Blad nie mozna odszukac zewnetrznej tabelki 2 stopnia dla pola {0}.", getName());
			t.printStackTrace();
		}
		return t1;
	}

	private Node getCollTableRow(Node element)
	{
		Node t2 = null;
		Node t1 = null;
		try
		{
			// paragraf cell row table
			t2 = element.getParentNode().getParentNode().getParentNode().getParentNode();
			// cell row table
			t1 = t2.getParentNode().getParentNode().getParentNode();
		}
		catch (Throwable t)
		{
			t.printStackTrace();
		}
		return t1;
	}

	@Override
	public void generateTables(Object bean)
	{
		Set<?> collection = null;
		try
		{
			if (getGetter() != null)
			{
				collection = (Set<?>) getGetter().invoke(bean, null);
				log.info("Uruchomienie {0} zwrocilo {1}", getGetter(), collection);
			}
		}
		catch (Exception e)
		{
			log.error("Blad wywolania metody {0} na rzecz obiektu {1}", getGetter(), bean);
			e.printStackTrace();
		}
		if (collection != null)
		{
			Integer lp = 1;
			for (Object object : collection)
			{
				Object entity = object;
				Object id = getEntityId(object);
				entity = entityManager.find(object.getClass(), id);
				setEntityLp(entity, lp);
				log.info("Komponent {0} ustawiony na pozycji {1}", entity, id);
				for (TableReference table : tables.values())
				{
					// Utworzenie pustych tabel, które będą potem wypełniane przez dodawanie wypełnionych wierszy szablonu
					for (FieldRef field : table.getFields().values())
					{
						// usunięcie wszystkich wierszy poza nagłowkiem tabeli 
						field.resetTable();
					}
					collRefStack.push(this, entity);
					for (FieldRef field : table.getFields().values())
					{
						field.generateTables(entity);
					}
					collRefStack.pop();
					collRefStack.generateOneTable(table.getTableDef());
					table.appendNewRow();
				}
				lp++;
			} //for (Object object : collection)
		}
	}

	public Object getEntityId(Object entity)
	{
		Class c = entity.getClass();
		Method[] methods = c.getDeclaredMethods();
		for (Method m : methods)
		{
			if (m.isAnnotationPresent(javax.persistence.Id.class))
			{
				Object id = null;
				try
				{
					id = m.invoke(entity, null);
				}
				catch (Exception e)
				{
					log.error(e.getStackTrace());
				}
				return id;
			}
		}
		return null;
	}
	
	public void setEntityLp(Object entity, int lp)
	{
		Class c = entity.getClass();
		Method setter = null;
		try
		{
			setter = c.getDeclaredMethod("setLp", new Class[] {Integer.class});
		}
		catch (Exception e)
		{
			log.info("Nie znaleziono metody do ustawiania wartosci pola Lp w klasie {0}", c.getName());
		}
		if (setter != null)
		{
			try
			{
				setter.invoke(entity, lp);
			}
			catch (Exception e)
			{
				log.error("Blad {0} wywolania metody setLp na rzecz obiektu {1}", e.getMessage(), entity);
			}
		}
	}

	public void removeTableRow()
	{
		log.info("metoda removeTableRow w CollRef {0}", getName());
		for (TableReference table : tables.values())
		{
			table.removeTableRow();
		}
	}

	public void resetTable()
	{
		for (TableReference table : tables.values())
		{
			table.resetTable();
		}
	}

	public void show()
	{
		log.info("CollectionRef");
		log.info("nazwa: {0}", getName());
		log.info("getter: {0}", getGetter());
		log.info("tables");
		for (TableReference table : tables.values())
		{
			table.show();
		}
		log.info("-------------------");
	}

	public void addField(FieldRef field)
	{
		TableReference tableRef = null;
		if (tables.containsKey(field.getTableDef()))
		{
			tableRef = tables.get(field.getTableDef());
		}
		else
		{
			tableRef = new TableReference(field.getTableDef(), field.getTableRow());
			tables.put(field.getTableDef(), tableRef);
		}
		tableRef.addField(field);
	}

	public void parse(OdfTextUserFieldGet element, String name, Object bean)
	{
		Class<?> c = bean.getClass();
		int dot = name.indexOf('.');
		if (dot >= 0)
		{
			String objName = name.substring(0, dot);
			String attrName = name.substring(dot + 1);
			Method getter = null;
			try
			{
				getter = c.getDeclaredMethod("get" + objName, null);
			}
			catch (Exception e)
			{
				log.error("Blad {2} gettera get{0} w obiekcie {1}.", attrName, objName, e.getMessage());
				e.printStackTrace();
			}
			Type type = getter.getGenericReturnType();
			String typeName = type.toString();
			if (typeName.startsWith("java.util.Set"))
			{
				String className = typeName.substring(14, typeName.length() - 1);
				Object bean1 = null;
				try
				{
					c = Class.forName(className);
					bean1 = c.newInstance();
				}
				catch (Exception e)
				{
					log.error("Blad utworzenia obiektu klasy {0}", className);
					e.printStackTrace();
				}
				CollRef collRef = null;
				TableReference tableRef = null;
				Node tableDef = getCollTableDef(element);
				if (tables.containsKey(tableDef))
				{
					tableRef = tables.get(tableDef);
					if (tableRef.fields.containsKey(getter))
					{
						collRef = (CollRef) tableRef.fields.get(getter);
					}
				}
				else
				{
					Node tableRow = null;
					if (tableDef != null)
					{
						tableRow = getCollTableRow(element);
					}
					tableRef = new TableReference(tableDef, tableRow);
				}
				if (collRef == null)
				{
					collRef = new CollRef(objName, element, getter);
					tableRef.addField(collRef);
				}
				collRef.parse(element, attrName, bean1);
			} // Set
			else
			// Przypadek, kiedy pole z relacji jeden/wiele do jednego
			{
				String className = typeName.substring(6);
				try
				{
					c = Class.forName(className);
				}
				catch (Exception e)
				{
					log.error("Blad utworzenia klasy {0}", typeName);
					e.printStackTrace();
				}
				RelationRef relRef = new RelationRef(objName, element, getter);
				relRef.parse(element, attrName, c);
				addField(relRef);
			}
		} // dot >= 0
		else
		{
			Method getter = null;
			try
			{
				getter = c.getDeclaredMethod("get" + name, null);
			}
			catch (Exception e)
			{
				log.error("Blad wyszukania gettera get{0} w klasie {1}.", name, c.getName());
				//				e.printStackTrace();
			}
			FieldRef field = new FieldRef(name, element, getter);
			addField(field);
		}
	}

	public void generateOneTable(Node tableDef, Object entity)
	{
		if (tables.containsKey(tableDef))
		{
			TableReference table = tables.get(tableDef);
			for (FieldRef field : table.getFields().values())
			{
				if (field.getClass().equals(FieldRef.class))
					field.generateTables(entity);
			}
		}
	}

	public Map<Node, TableReference> getTables()
	{
		return tables;
	}

	public void setTables(Map<Node, TableReference> tables)
	{
		this.tables = tables;
	}

	public EntityManager getEntityManager()
	{
		return entityManager;
	}

	public void setEntityManager(EntityManager entityManager)
	{
		this.entityManager = entityManager;
	}

	public CollRefStack getCollRefStack()
	{
		return collRefStack;
	}

	public void setCollRefStack(CollRefStack collRefStack)
	{
		this.collRefStack = collRefStack;
	}
}
